<?php
/**
 * MineAdmin is committed to providing solutions for quickly building web applications
 * Please view the LICENSE file that was distributed with this source code,
 * For the full copyright and license information.
 * Thank you very much for using MineAdmin.
 *
 * @Author kiki
 * @Link   https://gitee.com/xmo/MineAdmin
 */

declare(strict_types=1);

namespace Api\Middleware;

use App\Common\Service\Tool\ConstantsService;
use App\Common\Service\Tool\RouteService;
use App\System\Service\SystemAppService;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Router\Dispatched;
use Mine\Event\ApiAfter;
use Mine\Event\ApiBefore;
use App\System\Model\SystemApi;
use App\System\Service\SystemApiService;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Context\Context;
use Mine\Exception\NormalStatusException;
use Mine\Helper\MineCode;
use Mine\MineRequest;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use function Swoole\Coroutine\Http\request;

class ApiMiddleware implements MiddlewareInterface
{
    /**
     * 事件调度器
     * @var EventDispatcherInterface
     */
    #[Inject]
    protected EventDispatcherInterface $evDispatcher;

    /**
     * api接口设置
     * @param ServerRequestInterface $request
     * @param RequestHandlerInterface $handler
     * @return ResponseInterface
     */
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        // 跨域设置
        $this->crossSetting($request);

        /**
         * 全局设置
         * 端口来源
         */
        $this->setting($request);

        // 接口限制,避免大规模流量攻击
        $this->rateLimit();


        $result = $handler->handle($request);
        // 日志记录
        /** @var RouteService $rouceService */
        $rouceService = make(RouteService::class);
        $route = $rouceService->getControllerAndAction($request);
        $apiData = [
            'route' => $route,
        ];
        $event = new ApiAfter(['apiData' => $apiData], $result);
        $this->evDispatcher->dispatch($event);

        return $event->getResult();

    }


    /**
     * 获取请求控制器与方法
     * @return array
     */
    protected function getControllerAndAction($request)
    {
        $action = $request->getAttribute(Dispatched::class)->handler->callback;

        return ['controller' => $action[0], 'action' => $action[1]];
    }

    /**
     * 跨域设置
     * @param $request
     */
    protected function crossSetting($request): void
    {
        $crossData = [
            'Access-Control-Allow-Origin' => '*',
            'Access-Control-Allow-Methods' => 'POST,GET,PUT,DELETE,OPTIONS',
            'Access-Control-Allow-Headers' => 'Version, Access-Token, User-Token, Api-Auth, User-Agent, Keep-Alive, Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With',
            'Access-Control-Allow-Credentials' => 'true'
        ];

        foreach ($crossData as $name => $value) {
            $request->withHeader($name, $value);
        }
    }

    /**
     * 全局设置
     * @param ServerRequestInterface $request
     * @return void
     */
    protected function setting(ServerRequestInterface $request)
    {
        // 请求来源：app、h5、wechat-h5、wechat-applet
        $source = $request->getHeaderLine(ConstantsService::API_SOURCE) ?: 'app';
        if ($source) {
            Context::set(ConstantsService::API_SOURCE, $source);
        }
    }


    /**
     * 接口速率限制
     * @return mixed
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    public function rateLimit()
    {
        $request = container()->get(MineRequest::class);
        $ip = $request->ip();


        if (redis()->get('ipLimit:' . $ip)) {
            throw new NormalStatusException('ip已被封禁1天');
        }

        if (redis()->get('ipBan:' . $ip)) {
            throw new NormalStatusException('ip已被封禁，请联系客服解禁');
        }

        $path = parse_url($request->url())['path'] ?? '';
        if (!empty($path)) {

            // 限流key
            $limitKey = $ip . ':' . $path . '_rate_limit_ip';
            $this->rateLimitDadta(1, $limitKey, 5, $request->ip());

            // 封禁key
            $banKey = $ip . ':' . $path . '_rate_ban_ip';
            $this->rateLimitDadta(2, $banKey, 10, $request->ip());
        }

    }


    public function rateLimitDadta($type, $key, $time, $ip)
    {
        $rateLimitIp = redis()->get($key);


        if ($rateLimitIp > $time) {
            if ($type == 1) {
                //封禁1天
                redis()->set('ipLimit:' . $ip, 1, 86400);
                throw new NormalStatusException('频繁请求，请稍后重试');
            }
            if ($type == 2) {
                //封禁半年
                redis()->set('ipBan:' . $ip, 1, 86400 * 180);
                throw new NormalStatusException('ip已被封禁，请联系客服解禁');
            }
        }

        if ($rateLimitIp) {
            redis()->incr($key);
        } else {
            redis()->set($key, 1, 1);
        }
    }

    /**
     * 设置协程上下文
     * @param array $data
     */
    private function _setApiData(array $data)
    {
        Context::set('apiData', $data);
    }

    /**
     * 获取协程上下文
     * @return array
     */
    private function _getApiData(): array
    {
        return Context::get('apiData', []);
    }
}